package com.zbkj.service.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaOrderShippingService;
import cn.binarywang.wx.miniapp.bean.shop.request.shipping.*;
import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.service.WxPayService;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.zbkj.dal.base.constants.*;
import com.zbkj.dal.base.enums.ExpressOrderType;
import com.zbkj.dal.base.enums.OrderType;
import com.zbkj.dal.common.exception.CrmebException;
import com.zbkj.dal.common.utils.*;
import com.zbkj.dal.config.CrmebConfig;
import com.zbkj.dal.entity.order.MerchantOrder;
import com.zbkj.dal.entity.order.Order;
import com.zbkj.dal.entity.user.UserToken;
import com.zbkj.dal.entity.wechat.WechatExceptions;
import com.zbkj.dal.entity.wechat.WechatPayInfo;
import com.zbkj.dal.pojo.response.WeChatJsSdkConfigResponse;
import com.zbkj.dal.pojo.response.WechatOpenUploadResponse;
import com.zbkj.dal.pojo.response.WechatPublicShareResponse;
import com.zbkj.dal.pojo.vo.*;
import com.zbkj.service.config.WxConfigProperties;
import com.zbkj.service.model.dto.order.OrderInvoiceDTO;
import com.zbkj.service.service.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 微信公用服务实现类
 * +----------------------------------------------------------------------
 * | CRMEB [ CRMEB赋能开发者，助力企业发展 ]
 * +----------------------------------------------------------------------
 * | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
 * +----------------------------------------------------------------------
 * | Licensed CRMEB并不是自由软件，未经许可不能去掉CRMEB相关版权
 * +----------------------------------------------------------------------
 * | Author: CRMEB Team <admin@crmeb.com>
 * +----------------------------------------------------------------------
 */
@Slf4j
@Service
public class WechatServiceImpl implements WechatService {

    private static final Logger logger = LoggerFactory.getLogger(WechatServiceImpl.class);

    @Autowired
    private CrmebConfig crmebConfig;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private RestTemplateUtil restTemplateUtil;
    @Autowired
    private SystemConfigService systemConfigService;
    @Autowired
    private WechatExceptionsService wechatExceptionsService;
    @Autowired
    private WechatPayInfoService wechatPayInfoService;
    @Resource
    private WxPayService wxPayService;
    @Resource
    private WxConfigProperties wxConfigProperties;
    @Autowired
    private WxMaOrderShippingService wxMaOrderShippingService;
    @Lazy
    @Resource
    private OrderInvoiceService orderInvoiceService;
    @Autowired
    private UserTokenService userTokenService;
    @Resource
    private Environment environment;

    @Value("${wx.miniapp.privateKeyPath}")
    public String privateKeyPath;

    public String merchantSerialNumber = "4FAA87FB6246F4B3520CF2406E7F0CC319774DA7";

    public String apiV3Key = "8i2j1sft554w6jbzfvz6flo6m50y9ji7";

    public String merchantId = "1703251424";

    private static final DateTimeFormatter EXPIRE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00").withZone(ZoneId.of("Asia/Shanghai"));

    public static final String WE_CHAT_RESPONSE_SUCCESS_CODE = "SUCCESS";

    /**
     * 获取公众号accessToken
     */
    @Override
    public String getPublicAccessToken() {
        boolean exists = redisUtil.exists(WeChatConstants.REDIS_WECAHT_PUBLIC_ACCESS_TOKEN_KEY);
        if (exists) {
            Object accessToken = redisUtil.get(WeChatConstants.REDIS_WECAHT_PUBLIC_ACCESS_TOKEN_KEY);
            return accessToken.toString();
        }
        String appId = systemConfigService.getValueByKey(WeChatConstants.WECHAT_PUBLIC_APPID);
        if (StrUtil.isBlank(appId)) {
            throw new CrmebException("微信公众号appId未设置");
        }
        String secret = systemConfigService.getValueByKey(WeChatConstants.WECHAT_PUBLIC_APPSECRET);
        if (StrUtil.isBlank(secret)) {
            throw new CrmebException("微信公众号secret未设置");
        }
        WeChatAccessTokenVo accessTokenVo = getAccessToken(appId, secret, "public");
        // 缓存accessToken
        redisUtil.set(WeChatConstants.REDIS_WECAHT_PUBLIC_ACCESS_TOKEN_KEY, accessTokenVo.getAccessToken(),
                accessTokenVo.getExpiresIn().longValue() - 1800L, TimeUnit.SECONDS);
        return accessTokenVo.getAccessToken();
    }


    /**
     * 获取小程序accessToken
     *
     * @return accessToken
     */
    @Override
    public String getMiniAccessToken() {
        boolean exists = redisUtil.exists(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY);
        if (exists) {
            Object accessToken = redisUtil.get(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY);
            return accessToken.toString();
        }
        String appId = systemConfigService.getValueByKey(WeChatConstants.WECHAT_MINI_APPID);
        if (StrUtil.isBlank(appId)) {
            throw new CrmebException("微信小程序appId未设置");
        }
        String secret = systemConfigService.getValueByKey(WeChatConstants.WECHAT_MINI_APPSECRET);
        if (StrUtil.isBlank(secret)) {
            throw new CrmebException("微信小程序secret未设置");
        }
        WeChatAccessTokenVo accessTokenVo = getAccessToken(appId, secret, "mini");
        // 缓存accessToken
        redisUtil.set(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY, accessTokenVo.getAccessToken(),
                accessTokenVo.getExpiresIn().longValue() - 1800L, TimeUnit.SECONDS);
        return accessTokenVo.getAccessToken();
    }

    /**
     * 获取开放平台access_token
     * 通过 code 获取
     * 公众号使用
     *
     * @return 开放平台accessToken对象
     */
    @Override
    public WeChatOauthToken getOauth2AccessToken(String code) {
        String appId = systemConfigService.getValueByKey(WeChatConstants.WECHAT_PUBLIC_APPID);
        if (StrUtil.isBlank(appId)) {
            throw new CrmebException("微信公众号appId未设置");
        }
        String secret = systemConfigService.getValueByKey(WeChatConstants.WECHAT_PUBLIC_APPSECRET);
        if (StrUtil.isBlank(secret)) {
            throw new CrmebException("微信公众号secret未设置");
        }
        String url = StrUtil.format(WeChatConstants.WECHAT_OAUTH2_ACCESS_TOKEN_URL, appId, secret, code);
        JSONObject data = restTemplateUtil.getData(url);
        if (ObjectUtil.isNull(data)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                // 保存到微信异常表
                wxExceptionDispose(data, "微信获取开放平台access_token异常");
                throw new CrmebException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        return JSONObject.parseObject(data.toJSONString(), WeChatOauthToken.class);
    }

    /**
     * 获取开放平台用户信息
     *
     * @param accessToken 调用凭证
     * @param openid      普通用户的标识，对当前开发者帐号唯一
     *                    公众号使用
     * @return 开放平台用户信息对象
     */
    @Override
    public WeChatAuthorizeLoginUserInfoVo getSnsUserInfo(String accessToken, String openid) {
        String url = StrUtil.format(WeChatConstants.WECHAT_SNS_USERINFO_URL, accessToken, openid, "zh_CN");
        JSONObject data = restTemplateUtil.getData(url);
        if (ObjectUtil.isNull(data)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                // 保存到微信异常表
                wxExceptionDispose(data, "微信获取开放平台用户信息异常");
                throw new CrmebException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        return JSONObject.parseObject(data.toJSONString(), WeChatAuthorizeLoginUserInfoVo.class);
    }

    /**
     * 小程序登录凭证校验
     *
     * @return 小程序登录校验对象
     */
    @Override
    public WeChatMiniAuthorizeVo miniAuthCode(String code) {
        String appId = systemConfigService.getValueByKey(WeChatConstants.WECHAT_MINI_APPID);
        if (StrUtil.isBlank(appId)) {
            throw new CrmebException("微信小程序appId未设置");
        }
        String secret = systemConfigService.getValueByKey(WeChatConstants.WECHAT_MINI_APPSECRET);
        if (StrUtil.isBlank(secret)) {
            throw new CrmebException("微信小程序secret未设置");
        }
        String url = StrUtil.format(WeChatConstants.WECHAT_MINI_SNS_AUTH_CODE2SESSION_URL, appId, secret, code);
        JSONObject data = restTemplateUtil.getData(url);
        if (ObjectUtil.isNull(data)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                // 保存到微信异常表
                wxExceptionDispose(data, "微信小程序登录凭证校验异常");
                throw new CrmebException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        return JSONObject.parseObject(data.toJSONString(), WeChatMiniAuthorizeVo.class);
    }

    /**
     * 获取微信公众号js配置参数
     *
     * @return WeChatJsSdkConfigResponse
     */
    @Override
    public WeChatJsSdkConfigResponse getPublicJsConfig(String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new CrmebException("url无法解析！");
        }

        String appId = systemConfigService.getValueByKey(WeChatConstants.WECHAT_PUBLIC_APPID);
        if (StrUtil.isBlank(appId)) {
            throw new CrmebException("微信公众号appId未设置");
        }
        String ticket = getJsApiTicket();
        String nonceStr = CrmebUtil.getUuid();
        Long timestamp = DateUtil.currentSeconds();
        String signature = getJsSDKSignature(nonceStr, ticket, timestamp, url);

        WeChatJsSdkConfigResponse response = new WeChatJsSdkConfigResponse();
        response.setUrl(url);
        response.setAppId(appId);
        response.setNonceStr(nonceStr);
        response.setTimestamp(timestamp);
        response.setSignature(signature);
        response.setJsApiList(CrmebUtil.stringToArrayStr(WeChatConstants.PUBLIC_API_JS_API_SDK_LIST));
        response.setDebug(crmebConfig.isWechatJsApiDebug());
        return response;
    }

    /**
     * 生成小程序码
     *
     * @param jsonObject 微信端参数
     * @return 小程序码
     */
    @Override
    public String createQrCode(JSONObject jsonObject) {
        String miniAccessToken = getMiniAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_MINI_QRCODE_UNLIMITED_URL, miniAccessToken);
        byte[] bytes = restTemplateUtil.postJsonDataAndReturnBuffer(url, jsonObject);
        String response = new String(bytes);
        if (StringUtils.contains(response, "errcode")) {
            logger.error("微信生成小程序码异常" + response);
            JSONObject data = JSONObject.parseObject(response);
            // 保存到微信异常表
            wxExceptionDispose(data, "微信小程序生成小程序码异常");
            if (data.getString("errcode").equals("40001")) {
                redisUtil.delete(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY);
                miniAccessToken = getMiniAccessToken();
                url = StrUtil.format(WeChatConstants.WECHAT_MINI_QRCODE_UNLIMITED_URL, miniAccessToken);
                bytes = restTemplateUtil.postJsonDataAndReturnBuffer(url, jsonObject);
                response = new String(bytes);
                if (StringUtils.contains(response, "errcode")) {
                    logger.error("微信生成小程序码重试异常" + response);
                    JSONObject data2 = JSONObject.parseObject(response);
                    // 保存到微信异常表
                    wxExceptionDispose(data2, "微信小程序重试生成小程序码异常");
                } else {
                    try {
                        return CrmebUtil.getBase64Image(Base64.encodeBase64String(bytes));
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new CrmebException("微信小程序码转换Base64异常");
                    }
                }
            }
            throw new CrmebException("微信生成二维码异常");
        }
        try {
            return CrmebUtil.getBase64Image(Base64.encodeBase64String(bytes));
        } catch (Exception e) {
            e.printStackTrace();
            throw new CrmebException("微信小程序码转换Base64异常");
        }
    }

    /**
     * 微信公众号发送模板消息
     *
     * @param templateMessage 模板消息对象
     * @return 是否发送成功
     */
    @Override
    public Boolean sendPublicTemplateMessage(TemplateMessageVo templateMessage) {
        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_SEND_TEMPLATE_URL, accessToken);
        JSONObject jsonData = JSONObject.parseObject(JSONObject.toJSONString(templateMessage));
        String result = restTemplateUtil.postJsonData(url, jsonData);
        JSONObject data = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(data)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                wxExceptionDispose(data, "微信公众号发送模板消息异常");
                throw new CrmebException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        return Boolean.TRUE;
    }

    /**
     * 微信小程序发送订阅消息
     *
     * @param templateMessage 消息对象
     * @return 是否发送成功
     */
    @Override
    public Boolean sendMiniSubscribeMessage(ProgramTemplateMessageVo templateMessage) {
        String accessToken = getMiniAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_MINI_SEND_SUBSCRIBE_URL, accessToken);
        JSONObject messAge = JSONObject.parseObject(JSONObject.toJSONString(templateMessage));
        String result = restTemplateUtil.postJsonData(url, messAge);
        JSONObject data = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(data)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.getString("errcode").equals("40001")) {
                wxExceptionDispose(data, "微信小程序发送订阅消息异常");
                redisUtil.delete(WeChatConstants.REDIS_WECAHT_MINI_ACCESS_TOKEN_KEY);
                accessToken = getMiniAccessToken();
                url = StrUtil.format(WeChatConstants.WECHAT_MINI_SEND_SUBSCRIBE_URL, accessToken);
                result = restTemplateUtil.postJsonData(url, messAge);
                JSONObject data2 = JSONObject.parseObject(result);
                if (data2.containsKey("errcode") && !data2.getString("errcode").equals("0")) {
                    if (data2.containsKey("errmsg")) {
                        wxExceptionDispose(data2, "微信小程序发送订阅消息重试异常");
                        throw new CrmebException("微信接口调用失败：" + data2.getString("errcode") + data2.getString("errmsg"));
                    }
                } else {
                    return Boolean.TRUE;
                }
            }
            if (data.containsKey("errmsg")) {
                wxExceptionDispose(data, "微信小程序发送订阅消息异常");
                throw new CrmebException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        return Boolean.TRUE;
    }

    /**
     * 获取微信公众号自定义菜单配置
     * （使用本自定义菜单查询接口可以获取默认菜单和全部个性化菜单信息）
     *
     * @return 公众号自定义菜单
     */
    @Override
    public JSONObject getPublicCustomMenu() {
        JSONObject jsonObject = redisUtil.get(WeChatConstants.REDIS_PUBLIC_MENU_KEY);
        if (ObjectUtil.isNotNull(jsonObject) && StrUtil.isNotBlank(jsonObject.toString())) {
            return jsonObject;
        }
        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_MENU_GET_URL, accessToken);
        JSONObject result = restTemplateUtil.getData(url);
        if (ObjectUtil.isNull(result)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (result.containsKey("errcode") && result.getString("errcode").equals("0")) {
            return result;
        }
        if (result.containsKey("errmsg")) {
            wxExceptionDispose(result, "微信公众号获取自定义菜单配置异常");
            throw new CrmebException("微信接口调用失败：" + result.getString("errcode") + result.getString("errmsg"));
        }
        redisUtil.set(WeChatConstants.REDIS_PUBLIC_MENU_KEY, result);
        return result;
    }

    /**
     * 创建微信自定义菜单
     *
     * @param data 菜单json字符串 创建数据格式从微信菜单接口中获取 编辑后通过此接口提交给微信
     * @return 创建结果
     */
    @Override
    public Boolean createPublicCustomMenu(String data) {
        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_MENU_CREATE_URL, accessToken);
        String result = restTemplateUtil.postJsonData(url, JSONObject.parseObject(data));
        logger.info("微信消息发送结果:" + result);
        JSONObject jsonObject = JSONObject.parseObject(result);
        if (ObjectUtil.isNull(jsonObject)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (jsonObject.containsKey("errcode") && jsonObject.getString("errcode").equals("0")) {
            // 清除历史缓存
            if (redisUtil.exists(WeChatConstants.REDIS_PUBLIC_MENU_KEY)) {
                redisUtil.delete(WeChatConstants.REDIS_PUBLIC_MENU_KEY);
            }
            return Boolean.TRUE;
        }
        if (jsonObject.containsKey("errmsg")) {
            wxExceptionDispose(jsonObject, "微信公众号创建自定义菜单异常");
            throw new CrmebException("微信接口调用失败：" + jsonObject.getString("errcode") + jsonObject.getString("errmsg"));
        }
        return Boolean.TRUE;
    }

    /**
     * 删除微信自定义菜单
     *
     * @return 删除结果
     */
    @Override
    public Boolean deletePublicCustomMenu() {
        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_MENU_DELETE_URL, accessToken);
        JSONObject result = restTemplateUtil.getData(url);
        if (ObjectUtil.isNull(result)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (result.containsKey("errcode") && result.getString("errcode").equals("0")) {
            return Boolean.TRUE;
        }
        if (result.containsKey("errmsg")) {
            wxExceptionDispose(result, "微信公众号删除自定义菜单异常");
            throw new CrmebException("微信接口调用失败：" + result.getString("errcode") + result.getString("errmsg"));
        }
        // 清除历史缓存
        if (redisUtil.exists(WeChatConstants.REDIS_PUBLIC_MENU_KEY)) {
            redisUtil.delete(WeChatConstants.REDIS_PUBLIC_MENU_KEY);
        }
        return Boolean.TRUE;
    }

    /**
     * 企业号上传其他类型永久素材
     * 获取url
     *
     * @param type 素材类型:图片（image）、语音（voice）、视频（video），普通文件(file)
     */
    @Override
    public String qyapiAddMaterialUrl(String type) {
        String accessToken = getPublicAccessToken();
        return StrUtil.format(WeChatConstants.WECHAT_PUBLIC_QYAPI_ADD_MATERIAL_URL, type, accessToken);
    }

    /**
     * 微信开放平台上传素材
     *
     * @param file 文件
     * @param type 类型 图片（image）、语音（voice)
     * @return WechatOpenUploadResponse
     */
    @Override
    public WechatOpenUploadResponse openMediaUpload(MultipartFile file, String type) {
        if (StrUtil.isBlank(type)) {
            throw new CrmebException("请选择素材类型");
        }
        if (!"image".equals(type) && !"voice".equals(type)) {
            throw new CrmebException("未知的素材类型");
        }
        String[] split = Objects.requireNonNull(file.getOriginalFilename()).split("\\.");
        String suffixName = split[split.length - 1];
        isValidPic(file.getSize(), suffixName, type);

        // 需要注意：这个是微信2016年的企业号上传素材接口，企业号在微信的最后维护时间在2016-07-19
        String url = qyapiAddMaterialUrl(type);
        InputStream inputStream;
        try {
            inputStream = file.getResource().getInputStream();
        } catch (Exception e) {
            logger.error("获取文件输入流失败");
            throw new CrmebException(e.getMessage());
        }
        JSONObject uploadFile = UploadWeChatMediaUtil.uploadFile(url, inputStream, file.getOriginalFilename());
        if (ObjectUtil.isNull(uploadFile) || !uploadFile.containsKey("media_id")) {
            String message = ObjectUtil.isNotNull(uploadFile) ? uploadFile.getString("errmsg") : "";
            throw new CrmebException("素材上传失败" + message);
        }

        WechatOpenUploadResponse response = new WechatOpenUploadResponse();
        response.setMediaId(uploadFile.getString("media_id"));
        response.setUrl(uploadFile.getString("url"));
        response.setName(file.getOriginalFilename().replace(suffixName, ""));
        return response;

    }

    /**
     * 微信公众号分享配置
     */
    @Override
    public WechatPublicShareResponse getPublicShare() {
        WechatPublicShareResponse response = new WechatPublicShareResponse();
        response.setImage(systemConfigService.getValueByKey(SysConfigConstants.CONFIG_KEY_ADMIN_WECHAT_SHARE_IMAGE));
        response.setTitle(systemConfigService.getValueByKey(SysConfigConstants.CONFIG_KEY_ADMIN_WECHAT_SHARE_TITLE));
        response.setSynopsis(systemConfigService.getValueByKey(SysConfigConstants.CONFIG_KEY_ADMIN_WECHAT_SHARE_SYNOPSIS));
        return response;
    }

    /**
     * 获取JS-SDK的签名
     *
     * @param nonceStr  随机字符串
     * @param ticket    ticket
     * @param timestamp 时间戳
     * @param url       url
     * @return 签名
     */
    private String getJsSDKSignature(String nonceStr, String ticket, Long timestamp, String url) {
        //注意这里参数名必须全部小写，且必须有序
        String paramString = StrUtil.format("jsapi_ticket={}&noncestr={}&timestamp={}&url={}", ticket, nonceStr, timestamp, url);
        return SecureUtil.sha1(paramString);
    }

    /**
     * 获取JS-SDK的ticket
     * 用于计算签名
     *
     * @return ticket
     */
    private String getJsApiTicket() {
        boolean exists = redisUtil.exists(WeChatConstants.REDIS_PUBLIC_JS_API_TICKET);
        if (exists) {
            Object ticket = redisUtil.get(WeChatConstants.REDIS_PUBLIC_JS_API_TICKET);
            return ticket.toString();
        }
        String accessToken = getPublicAccessToken();
        String url = StrUtil.format(WeChatConstants.WECHAT_PUBLIC_JS_TICKET_URL, accessToken);
        JSONObject data = restTemplateUtil.getData(url);
        if (ObjectUtil.isNull(data)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                // 保存到微信异常表
                wxExceptionDispose(data, "微信获取JS-SDK的ticket异常");
                throw new CrmebException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        String ticket = data.getString("ticket");
        redisUtil.set(WeChatConstants.REDIS_PUBLIC_JS_API_TICKET, ticket, WeChatConstants.REDIS_PUBLIC_JS_API_TICKET_EXPRESS, TimeUnit.SECONDS);
        return ticket;
    }

    /**
     * 获取微信accessToken
     *
     * @param appId  appId
     * @param secret secret
     * @param type   mini-小程序，public-公众号，app-app
     * @return WeChatAccessTokenVo
     */
    private WeChatAccessTokenVo getAccessToken(String appId, String secret, String type) {
        String url = StrUtil.format(WeChatConstants.WECHAT_ACCESS_TOKEN_URL, appId, secret);
        JSONObject data = restTemplateUtil.getData(url);
        if (ObjectUtil.isNull(data)) {
            throw new CrmebException("微信平台接口异常，没任何数据返回！");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                // 保存到微信异常表
                wxExceptionDispose(data, StrUtil.format("微信获取accessToken异常，{}端", type));
                throw new CrmebException("微信接口调用失败：" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        return JSONObject.parseObject(data.toJSONString(), WeChatAccessTokenVo.class);
    }

    /**
     * 微信异常处理
     *
     * @param jsonObject 微信返回数据
     * @param remark     备注
     */
    private void wxExceptionDispose(JSONObject jsonObject, String remark) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        wechatExceptions.setErrcode(jsonObject.getString("errcode"));
        wechatExceptions.setErrmsg(StrUtil.isNotBlank(jsonObject.getString("errmsg")) ? jsonObject.getString("errmsg") : "");
        wechatExceptions.setData(jsonObject.toJSONString());
        wechatExceptions.setRemark(remark);
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }

    /**
     * 是否符合微信规范
     *
     * @param size       long 文件大小
     * @param suffixName String 后缀名
     */
    private void isValidPic(long size, String suffixName, String type) {
        JSONObject config = getMediaUploadConfig();
        long supportSize = config.getJSONObject(type).getLong("size");
        if (supportSize < size) {
            throw new CrmebException("文件大小不能超过" + supportSize);
        }
        String supportNameSuffix = config.getJSONObject(type).getString("suffix");
        List<String> suffixNameList = CrmebUtil.stringToArrayStr(supportNameSuffix);
        if (!suffixNameList.contains(suffixName)) {
            throw new CrmebException("文件格式必须是" + supportSize);
        }
    }

    /**
     * 获取素材上传配置
     */
    private JSONObject getMediaUploadConfig() {
        String data = StrUtil.format("{image:{size:\"{}\", suffix: \"{}\"}, voice:{size:\"{}\", suffix: \"{}\"}}",
                WeChatConstants.OPEN_MEDIA_UPLOAD_IMAGE_MAX_SIZE, WeChatConstants.OPEN_MEDIA_UPLOAD_IMAGE_SUFFIX_NAME,
                WeChatConstants.OPEN_MEDIA_UPLOAD_VOICE_MAX_SIZE, WeChatConstants.OPEN_MEDIA_UPLOAD_VOICE_SUFFIX_NAME);
        return JSONObject.parseObject(data);
    }

    // ===================================== 微信订单相关 ======================================

    /**
     * 微信预下单接口(统一下单)
     *
     * @param unifiedOrderVo 预下单请求对象
     * @return 微信预下单返回对象
     */
    public CreateOrderResponseVo payUnifiedorder(CreateOrderRequestVo unifiedOrderVo) {
        WxPayUnifiedOrderV3Request request = this.transformPayRequestV3(unifiedOrderVo);
        WechatPayInfo wechatPayInfo = createWechatPayInfo(unifiedOrderVo);
        WxPayUnifiedOrderV3Result wxPayUnifiedOrderV3Result = null;
        try {
            log.info("调用微信预下单请求参数为：{}", JSONUtil.toJsonStr(request));
            //wxPayUnifiedOrderV3Result = wxPayService.unifiedOrderV3(TradeTypeEnum.JSAPI, request);
            wxPayUnifiedOrderV3Result = this.transactions(request);

            log.info("wxPayUnifiedOrderV3Result：{}", JSONUtil.toJsonStr(wxPayUnifiedOrderV3Result));

            WxPayUnifiedOrderV3Result.JsapiResult jsapiResult = wxPayUnifiedOrderV3Result.getPayInfo(TradeTypeEnum.JSAPI, request.getAppid(), request.getMchid(), this.getPrivateKey(privateKeyPath));
            log.info("调用微信预下单响应结果为：{}", JSONUtil.toJsonStr(jsapiResult));
            CreateOrderResponseVo responseVo = transformPayResult(jsapiResult, wxPayUnifiedOrderV3Result.getPrepayId(), unifiedOrderVo.getMch_id());

            wechatPayInfo.setErrCode("200");
            wechatPayInfo.setPrepayId(responseVo.getPrepayId());
            wechatPayInfoService.save(wechatPayInfo);

            responseVo.setExtra(unifiedOrderVo.getScene_info());
            return responseVo;
        } catch (Exception e) {
            wechatPayInfo.setErrCode("-200");
            wechatPayInfoService.save(wechatPayInfo);

            // 记录微信调用异常信息
            wxPayExceptionDispose(wxPayUnifiedOrderV3Result, e.getMessage());
            log.error("微信预下单接口调用失败，请求参数为：{}，异常信息如下：", JSONUtil.toJsonStr(unifiedOrderVo), e);
            throw new CrmebException(e.getMessage());
        }

    }


    public WxPayUnifiedOrderV3Result transactions(WxPayUnifiedOrderV3Request wxPayUnifiedOrderV3Request) {
        String prepayId = null;
        String key = redisUtil.get(wxPayUnifiedOrderV3Request.getOutTradeNo());
        log.info("key>>>>{}", key);
        if (StringUtils.isNotBlank(key)) {
            prepayId = key;
        } else {
            // 使用自动更新平台证书的RSA配置
            Config config =
                    new RSAAutoCertificateConfig.Builder()
                            .merchantId(merchantId)
                            .privateKeyFromPath(privateKeyPath)
                            .merchantSerialNumber(merchantSerialNumber)
                            .apiV3Key(apiV3Key)
                            .build();
            JsapiService service = new JsapiService.Builder().config(config).build();
            log.info("<<<<<<<<request>>>");
            PrepayRequest request = new PrepayRequest();
            Amount amount = new Amount();
            amount.setTotal(wxPayUnifiedOrderV3Request.getAmount().getTotal());
            request.setAmount(amount);
            request.setAppid(wxPayUnifiedOrderV3Request.getAppid());
            request.setMchid(wxPayUnifiedOrderV3Request.getMchid());
            request.setDescription(wxPayUnifiedOrderV3Request.getDescription());
            request.setNotifyUrl(wxPayUnifiedOrderV3Request.getNotifyUrl());
            request.setOutTradeNo(wxPayUnifiedOrderV3Request.getOutTradeNo());
            request.setTimeExpire(wxPayUnifiedOrderV3Request.getTimeExpire());
            request.setAttach(wxPayUnifiedOrderV3Request.getAttach());
            Payer payer = new Payer();
            payer.setOpenid(wxPayUnifiedOrderV3Request.getPayer().getOpenid());
            request.setPayer(payer);
            log.info("request>>>{}", JSON.toJSONString(request));
            PrepayResponse response = service.prepay(request);
            if (StringUtils.isBlank(response.getPrepayId())) {
                throw new CrmebException("订单下单失败");
            }
            prepayId = response.getPrepayId();
            //缓存24小时 24小时内可以通过缓存继续支付
            redisUtil.set(wxPayUnifiedOrderV3Request.getOutTradeNo(), prepayId, 24L, TimeUnit.HOURS);
        }
        WxPayUnifiedOrderV3Result wxPayUnifiedOrderV3Result = new WxPayUnifiedOrderV3Result();
        wxPayUnifiedOrderV3Result.setPrepayId(prepayId);
        wxPayUnifiedOrderV3Result.setH5Url(null);
        wxPayUnifiedOrderV3Result.setCodeUrl(null);
        return wxPayUnifiedOrderV3Result;

    }

    public String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    @SneakyThrows
    private String getSign(String signatureStr, String privateKeyPath) {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(this.getPrivateKey(privateKeyPath));
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }

    /**
     * 获取私钥。
     * 证书路径 本地使用如： D:\\微信平台证书工具\\7.9\\apiclient_key.pem
     * 证书路径 线上使用如： /usr/apiclient_key.pem
     * String filename 私钥文件路径  (required)
     *
     * @return 私钥对象
     */
    public PrivateKey getPrivateKey(String privateKeyPath) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(privateKeyPath)), StandardCharsets.UTF_8);
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(cn.hutool.core.codec.Base64.decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    @Override
    public void wxRefund(String outTradeNo, String refundOrderNo, BigDecimal refundPrice, BigDecimal
            totalPrice, String reason) {
        WxPayRefundV3Request refundInfo = new WxPayRefundV3Request();
        String apiDomain = systemConfigService.getValueByKeyException(SysConfigConstants.CONFIG_KEY_API_URL);
        refundInfo.setOutTradeNo(outTradeNo);
        refundInfo.setOutRefundNo(refundOrderNo);
        refundInfo.setReason(reason);
        refundInfo.setNotifyUrl(apiDomain + PayConstants.WX_PAY_REFUND_NOTIFY_API_URI_V3);
        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
        if ("test".equals(environment.getActiveProfiles()[0])) {
            amount.setRefund(1);
            amount.setTotal(1);
            amount.setCurrency("CNY");
        } else {
            amount.setRefund(MoneyUtil.yuan2Fen(refundPrice));
            amount.setTotal(MoneyUtil.yuan2Fen(totalPrice));
            amount.setCurrency("CNY");
        }
        refundInfo.setAmount(amount);

        WxPayRefundV3Result wxPayRefundV3Result;
        try {
            log.info("调用微信预下单请求参数为：{}", JSONUtil.toJsonStr(refundInfo));
            wxPayRefundV3Result = wxPayService.refundV3(refundInfo);
            log.info("调用微信预下单响应结果为：{}", JSONUtil.toJsonStr(wxPayRefundV3Result));
        } catch (Exception e) {
            wxRefundExceptionDispose(null, e.getMessage());
            log.error("微信退款接口错误信息,请求参数为：{}，异常信息如下:\n", JSONUtil.toJsonStr(refundInfo), e);
            throw new CrmebException("微信退款失败，请稍后重试");
        }

        if (ObjectUtil.isNull(wxPayRefundV3Result) || "CLOSED".equals(wxPayRefundV3Result.getStatus()) || "ABNORMAL".equals(wxPayRefundV3Result.getStatus())) {
            wxRefundExceptionDispose(wxPayRefundV3Result, ObjectUtil.isNull(wxPayRefundV3Result) ? null : wxPayRefundV3Result.getStatus());
            log.error("微信服务调用失败，响应结果如下：{}", JSONUtil.toJsonStr(wxPayRefundV3Result));
            throw new CrmebException("微信服务调用失败");
        }
    }

    /**
     * 微信支付查询订单
     *
     * @return 支付订单查询结果
     */
    @Override
    public WxPayOrderQueryV3Result payOrderQueryV3(String outTradeNo) {
        WxPayOrderQueryV3Result wxPayOrderQueryResult = null;
        try {
            WxPayOrderQueryV3Request wxPayOrderQueryRequest = new WxPayOrderQueryV3Request();
            wxPayOrderQueryRequest.setMchid(wxConfigProperties.getMchId());
            wxPayOrderQueryRequest.setOutTradeNo(outTradeNo);
            wxPayOrderQueryResult = wxPayService.queryOrderV3(wxPayOrderQueryRequest);
        } catch (Exception e) {
            wxPayQueryExceptionDispose(wxPayOrderQueryResult, "微信支付查询订单通信异常");
            log.error("查询订单支付状态异常，异常信息如下：", e);
            throw new CrmebException("查询订单支付状态异常，请稍后重试");
        }

        if (ObjectUtil.isNull(wxPayOrderQueryResult)) {
            wxPayQueryExceptionDispose(wxPayOrderQueryResult, "微信支付查询订单通信异常");
            log.error("查询订单支付状态异常，查询结果异常，单号为：{}，响应结果为空", outTradeNo);
            throw new CrmebException("查询订单支付状态异常，请稍后重试");
        }

        return wxPayOrderQueryResult;
    }

    /**
     * 微信申请退款
     *
     * @param wxRefundVo 微信申请退款对象
     * @param path       商户p12证书绝对路径
     * @return 申请退款结果对象
     */
    @Deprecated
    @Override
    public WxRefundResponseVo payRefund(WxRefundVo wxRefundVo, String path) {
        String xmlStr = XmlUtil.objectToXml(wxRefundVo);
        String url = WeChatConstants.PAY_API_URL + WeChatConstants.PAY_REFUND_API_URI_WECHAT;
        HashMap<String, Object> map = new HashMap<>();
        String xml = "";
        try {
            xml = restTemplateUtil.postWXRefundXml(url, xmlStr, wxRefundVo.getMch_id(), path);
            map = XmlUtil.xmlToMap(xml);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CrmebException("xmlToMap错误，xml = " + xml);
        }
        if (null == map) {
            throw new CrmebException("微信无信息返回，微信申请退款失败！");
        }

        WxRefundResponseVo responseVo = CrmebUtil.mapToObj(map, WxRefundResponseVo.class);
        if (responseVo.getReturnCode().equalsIgnoreCase("FAIL")) {
            wxPayExceptionDispose(map, "微信申请退款异常1");
            throw new CrmebException("微信申请退款失败1！" + responseVo.getReturnMsg());
        }

        if (responseVo.getResultCode().equalsIgnoreCase("FAIL")) {
            wxPayExceptionDispose(map, "微信申请退款业务异常");
            throw new CrmebException("微信申请退款失败2！" + responseVo.getErrCodeDes());
        }
        System.out.println("================微信申请退款结束=========================");
        return responseVo;
    }

    public CreateOrderResponseVo payUnifiedorderV2(CreateOrderRequestVo unifiedorderVo) {
        try {
            String url = PayConstants.WX_PAY_API_URL + PayConstants.WX_PAY_API_URI;
            String request = XmlUtil.objectToXml(unifiedorderVo);
            String xml = restTemplateUtil.postXml(url, request);
            logger.info("微信预下单接口请求参数：" + request);
            HashMap<String, Object> map = XmlUtil.xmlToMap(xml);
            if (null == map) {
                throw new CrmebException("微信下单失败！");
            }
            // 保存微信预下单
            WechatPayInfo wechatPayInfo = createWechatPayInfo(unifiedorderVo);

            CreateOrderResponseVo responseVo = CrmebUtil.mapToObj(map, CreateOrderResponseVo.class);
            if (responseVo.getReturnCode().equalsIgnoreCase("FAIL")) {
                // 保存到微信异常表
                wxPayExceptionDispose(map, "微信支付预下单异常");
                wechatPayInfo.setErrCode(map.get("return_code").toString());
                wechatPayInfoService.save(wechatPayInfo);
                throw new CrmebException("微信下单失败1！" + responseVo.getReturnMsg());
            }

            if (responseVo.getResultCode().equalsIgnoreCase("FAIL")) {
                wxPayExceptionDispose(map, "微信支付预下单业务异常");
                wechatPayInfo.setErrCode(map.get("err_code").toString());
                wechatPayInfoService.save(wechatPayInfo);
                throw new CrmebException("微信下单失败2！" + responseVo.getErrCodeDes());
            }
            wechatPayInfo.setErrCode("200");
            wechatPayInfo.setPrepayId(responseVo.getPrepayId());
            wechatPayInfoService.save(wechatPayInfo);
            responseVo.setExtra(unifiedorderVo.getScene_info());
            return responseVo;
        } catch (Exception e) {
            e.printStackTrace();
            throw new CrmebException(e.getMessage());
        }
    }

    /**
     * 微信支付查询订单
     *
     * @return 支付订单查询结果
     */
    @Override
    public WxPayOrderQueryResult payOrderQuery(String outTradeNo) {
        WxPayOrderQueryResult wxPayOrderQueryResult = null;
        try {
            WxPayOrderQueryRequest wxPayOrderQueryRequest = new WxPayOrderQueryRequest();
            wxPayOrderQueryRequest.setVersion("1.0");
            wxPayOrderQueryRequest.setOutTradeNo(outTradeNo);
            wxPayOrderQueryResult = wxPayService.queryOrder(wxPayOrderQueryRequest);
        } catch (Exception e) {
            wxPayQueryExceptionDispose(wxPayOrderQueryResult, "微信支付查询订单通信异常");
            log.error("查询订单支付状态异常，异常信息如下：", e);
            throw new CrmebException("查询订单支付状态异常，请稍后重试");
        }

        if (ObjectUtil.isNull(wxPayOrderQueryResult) || isFail(wxPayOrderQueryResult)) {
            wxPayQueryExceptionDispose(wxPayOrderQueryResult, "微信支付查询订单通信异常");
            log.error("查询订单支付状态异常，查询结果异常，单号为：{}，响应结果为：{}", outTradeNo,
                    ObjectUtil.isNull(wxPayOrderQueryResult) ? null : JSONUtil.toJsonStr(wxPayOrderQueryResult));
            throw new CrmebException("查询订单支付状态异常，请稍后重试");
        }

        return wxPayOrderQueryResult;
    }

    @Deprecated
    @Override
    public MyRecord payOrderQuery(Map<String, String> payVo) {
        String url = PayConstants.WX_PAY_API_URL + PayConstants.WX_PAY_ORDER_QUERY_API_URI;
        try {
            String request = XmlUtil.mapToXml(payVo);
            String xml = restTemplateUtil.postXml(url, request);
            HashMap<String, Object> map = XmlUtil.xmlToMap(xml);
            MyRecord record = new MyRecord();
            if (null == map) {
                throw new CrmebException("微信订单查询失败！");
            }
            record.setColums(map);
            if (record.getStr("return_code").equalsIgnoreCase("FAIL")) {
                wxPayQueryExceptionDispose(record, "微信支付查询订单通信异常");
                throw new CrmebException("微信订单查询失败1！" + record.getStr("return_msg"));
            }

            if (record.getStr("result_code").equalsIgnoreCase("FAIL")) {
                wxPayQueryExceptionDispose(record, "微信支付查询订单结果异常");
                throw new CrmebException("微信订单查询失败2！" + record.getStr("err_code") + record.getStr("err_code_des"));
            }
            if (!record.getStr("trade_state").equalsIgnoreCase("SUCCESS")) {
                wxPayQueryExceptionDispose(record, "微信支付查询订单状态异常");
                throw new CrmebException("微信订单支付失败3！" + record.getStr("trade_state"));
            }

            return record;
        } catch (Exception e) {
            e.printStackTrace();
            throw new CrmebException("查询微信订单mapToXml异常===》" + e.getMessage());
        }
    }

    /**
     * 微信支付异常处理
     *
     * @param wxPayUnifiedOrderResult 微信返回数据
     */
    private void wxPayExceptionDispose(WxPayUnifiedOrderV3Result wxPayUnifiedOrderResult, String errMsg) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        if (ObjectUtil.isNull(wxPayUnifiedOrderResult)) {
            wechatExceptions.setData(null);
        } else {
            wechatExceptions.setData(JSONObject.toJSONString(wxPayUnifiedOrderResult));
        }
        wechatExceptions.setErrcode("-100");
        wechatExceptions.setErrmsg(errMsg);
        wechatExceptions.setRemark("微信支付预下单异常");
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }

    private void wxPayExceptionDispose(WxPayUnifiedOrderResult wxPayUnifiedOrderResult, String remark) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        String returnCode = wxPayUnifiedOrderResult.getReturnCode();
        if (returnCode.equalsIgnoreCase("FAIL")) {
            wechatExceptions.setErrcode("-100");
            wechatExceptions.setErrmsg(wxPayUnifiedOrderResult.getReturnMsg());
        } else {
            wechatExceptions.setErrcode(wxPayUnifiedOrderResult.getErrCode());
            wechatExceptions.setErrmsg(wxPayUnifiedOrderResult.getErrCodeDes());
        }
        wechatExceptions.setData(JSONObject.toJSONString(wxPayUnifiedOrderResult));
        wechatExceptions.setRemark(remark);
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }

    private void wxRefundExceptionDispose(WxPayRefundV3Result wxPayRefundV3Result, String errMsg) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        if (ObjectUtil.isNull(wxPayRefundV3Result)) {
            wechatExceptions.setData(null);
        } else {
            wechatExceptions.setData(JSONObject.toJSONString(wxPayRefundV3Result));
        }
        wechatExceptions.setErrcode("-100");
        wechatExceptions.setErrmsg(errMsg);
        wechatExceptions.setRemark("微信申请退款失败");
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }

    private void wxPayExceptionDispose(HashMap<String, Object> map, String remark) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        String returnCode = (String) map.get("return_code");
        if (returnCode.equalsIgnoreCase("FAIL")) {
            wechatExceptions.setErrcode("-100");
            wechatExceptions.setErrmsg(map.get("return_msg").toString());
        } else {
            wechatExceptions.setErrcode(map.get("err_code").toString());
            wechatExceptions.setErrmsg(map.get("err_code_des").toString());
        }
        wechatExceptions.setData(JSONObject.toJSONString(map));
        wechatExceptions.setRemark(remark);
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }

    /**
     * 微信支付查询异常处理
     *
     * @param record 微信返回数据
     * @param remark 备注
     */
    private void wxPayQueryExceptionDispose(MyRecord record, String remark) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        if (record.getStr("return_code").equalsIgnoreCase("FAIL")) {
            wechatExceptions.setErrcode("-200");
            wechatExceptions.setErrmsg(record.getStr("return_msg"));
        } else if (record.getStr("result_code").equalsIgnoreCase("FAIL")) {
            wechatExceptions.setErrcode(record.getStr("err_code"));
            wechatExceptions.setErrmsg(record.getStr("err_code_des"));
        } else if (!record.getStr("trade_state").equalsIgnoreCase("SUCCESS")) {
            wechatExceptions.setErrcode("-201");
            wechatExceptions.setErrmsg(record.getStr("trade_state"));
        }
        wechatExceptions.setData(JSONObject.toJSONString(record.getColumns()));
        wechatExceptions.setRemark(remark);
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }

    private void wxPayQueryExceptionDispose(WxPayOrderQueryResult wxPayOrderQueryResult, String remark) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        if (ObjectUtil.isNull(wxPayOrderQueryResult)) {
            wechatExceptions.setErrcode("-202");
            wechatExceptions.setErrmsg("查询结果为空");
        } else {
            if ("FAIL".equalsIgnoreCase(wxPayOrderQueryResult.getReturnCode())) {
                wechatExceptions.setErrcode("-200");
                wechatExceptions.setErrmsg(wxPayOrderQueryResult.getReturnMsg());
            } else if ("FAIL".equalsIgnoreCase(wxPayOrderQueryResult.getResultCode())) {
                wechatExceptions.setErrcode(wxPayOrderQueryResult.getErrCode());
                wechatExceptions.setErrmsg(wxPayOrderQueryResult.getErrCodeDes());
            } else if (!"SUCCESS".equalsIgnoreCase(wxPayOrderQueryResult.getTradeState())) {
                wechatExceptions.setErrcode("-201");
                wechatExceptions.setErrmsg(wxPayOrderQueryResult.getTradeState());
            }
        }
        wechatExceptions.setData(JSONObject.toJSONString(wxPayOrderQueryResult));
        wechatExceptions.setRemark(remark);
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }

    private void wxPayQueryExceptionDispose(WxPayOrderQueryV3Result wxPayOrderQueryResult, String remark) {
        WechatExceptions wechatExceptions = new WechatExceptions();
        if (ObjectUtil.isNull(wxPayOrderQueryResult)) {
            wechatExceptions.setErrcode("-202");
            wechatExceptions.setErrmsg("查询结果为空");
        } else {
            wechatExceptions.setErrcode("-200");
            wechatExceptions.setErrmsg(wxPayOrderQueryResult.getTradeState());
        }
        wechatExceptions.setData(JSONObject.toJSONString(wxPayOrderQueryResult));
        wechatExceptions.setRemark(remark);
        wechatExceptions.setCreateTime(DateUtil.date());
        wechatExceptions.setUpdateTime(DateUtil.date());
        wechatExceptionsService.save(wechatExceptions);
    }

    public WxPayUnifiedOrderV3Request transformPayRequestV3(CreateOrderRequestVo unifiedOrderVo) {
        String apiDomain = systemConfigService.getValueByKeyException(SysConfigConstants.CONFIG_KEY_API_URL);
        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
        request.setAppid(unifiedOrderVo.getAppid());
        request.setMchid(unifiedOrderVo.getMch_id());
        request.setDescription(unifiedOrderVo.getBody());
        request.setOutTradeNo(unifiedOrderVo.getOut_trade_no());
        LocalDateTime expireTime = LocalDateTime.now().plusHours(15);
        request.setTimeExpire(expireTime.format(EXPIRE_TIME_FORMATTER));
        request.setAttach(unifiedOrderVo.getAttach());
        request.setNotifyUrl(apiDomain + PayConstants.WX_PAY_NOTIFY_API_URI_V3);
//            request.setGoodsTag();
//            request.setSupportFapiao();
        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
        // TODO JXY 测试代码
        if ("test".equals(environment.getActiveProfiles()[0])) {
            amount.setTotal(1);
        } else {
            amount.setTotal(unifiedOrderVo.getTotal_fee());
        }
        request.setAmount(amount);
        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
        payer.setOpenid(unifiedOrderVo.getOpenid());
        request.setPayer(payer);
//            request.setDetail();
//            request.setSceneInfo();
//            request.setSettleInfo();
        return request;
    }

    public WxPayUnifiedOrderRequest transformPayRequest(CreateOrderRequestVo unifiedOrderVo) {
        WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
        orderRequest.setBody(unifiedOrderVo.getBody());
        orderRequest.setOpenid(unifiedOrderVo.getOpenid());
        // orderRequest.setDetail()
        orderRequest.setTotalFee(unifiedOrderVo.getTotal_fee());
        orderRequest.setOutTradeNo(unifiedOrderVo.getOut_trade_no());
        orderRequest.setTradeType(WxPayConstants.TradeType.JSAPI);
        orderRequest.setSpbillCreateIp(unifiedOrderVo.getSpbill_create_ip());
        orderRequest.setNotifyUrl(unifiedOrderVo.getNotify_url());
        return orderRequest;
    }

    public CreateOrderResponseVo transformPayResult(WxPayUnifiedOrderV3Result.JsapiResult jsapiResult, String
            prepayId, String mchId) {
        CreateOrderResponseVo responseVo = new CreateOrderResponseVo();
        responseVo.setAppId(jsapiResult.getAppId());
        responseVo.setMchId(mchId);
//        responseVo.setDeviceInfo();
        responseVo.setNonceStr(jsapiResult.getNonceStr());
        responseVo.setSign(jsapiResult.getPaySign());
//        responseVo.setReturnCode(wxPayUnifiedOrderResult.getReturnCode());
//        responseVo.setReturnMsg(wxPayUnifiedOrderResult.getReturnMsg());
//        responseVo.setResultCode(wxPayUnifiedOrderResult.getResultCode());
//        responseVo.setErrCode(wxPayUnifiedOrderResult.getErrCode());
//        responseVo.setErrCodeDes(wxPayUnifiedOrderResult.getErrCodeDes());
        responseVo.setTradeType(TradeTypeEnum.JSAPI.name());
        responseVo.setPrepayId(prepayId);
//        responseVo.setMWebUrl(jsapiResult.get);
        return responseVo;
    }

    public CreateOrderResponseVo transformPayResult(WxPayUnifiedOrderResult
                                                            wxPayUnifiedOrderResult, CreateOrderRequestVo createOrderRequestVo) {
        CreateOrderResponseVo responseVo = new CreateOrderResponseVo();
        responseVo.setAppId(createOrderRequestVo.getAppid());
        responseVo.setMchId(createOrderRequestVo.getMch_id());
//        responseVo.setDeviceInfo();
        responseVo.setNonceStr(createOrderRequestVo.getNonce_str());
        responseVo.setSign(createOrderRequestVo.getSign());
        responseVo.setReturnCode(wxPayUnifiedOrderResult.getReturnCode());
        responseVo.setReturnMsg(wxPayUnifiedOrderResult.getReturnMsg());
        responseVo.setResultCode(wxPayUnifiedOrderResult.getResultCode());
        responseVo.setErrCode(wxPayUnifiedOrderResult.getErrCode());
        responseVo.setErrCodeDes(wxPayUnifiedOrderResult.getErrCodeDes());
        responseVo.setTradeType(TradeTypeEnum.JSAPI.name());
        responseVo.setPrepayId(wxPayUnifiedOrderResult.getPrepayId());
        responseVo.setMWebUrl(wxPayUnifiedOrderResult.getMwebUrl());
        return responseVo;
    }

    public CreateOrderResponseVo transformPayResult(WxPayUnifiedOrderV3Result
                                                            wxPayUnifiedOrderResult, CreateOrderRequestVo createOrderRequestVo) {
        CreateOrderResponseVo responseVo = new CreateOrderResponseVo();
        responseVo.setAppId(createOrderRequestVo.getAppid());
        responseVo.setMchId(createOrderRequestVo.getMch_id());
//        responseVo.setDeviceInfo();
        responseVo.setNonceStr(createOrderRequestVo.getNonce_str());
        responseVo.setSign(createOrderRequestVo.getSign());
//        responseVo.setReturnCode();
//        responseVo.setReturnMsg();
//        responseVo.setResultCode();
//        responseVo.setErrCode();
//        responseVo.setErrCodeDes();
        responseVo.setTradeType(TradeTypeEnum.JSAPI.name());
        responseVo.setPrepayId(wxPayUnifiedOrderResult.getPrepayId());
//        responseVo.setMWebUrl(wxPayUnifiedOrderResult.getH5Url());
        return responseVo;
    }

    /**
     * 生成微信订单表对象
     *
     * @param vo 预下单数据
     * @return WechatPayInfo
     */
    private WechatPayInfo createWechatPayInfo(CreateOrderRequestVo vo) {
        WechatPayInfo payInfo = new WechatPayInfo();
        payInfo.setAppId(vo.getAppid());
        payInfo.setMchId(vo.getMch_id());
        payInfo.setDeviceInfo(vo.getDevice_info());
        payInfo.setOpenId(vo.getOpenid());
        payInfo.setNonceStr(vo.getNonce_str());
        payInfo.setSign(vo.getSign());
        payInfo.setSignType(vo.getSign_type());
        payInfo.setBody(vo.getBody());
        payInfo.setDetail(vo.getDetail());
        payInfo.setAttach(vo.getAttach());
        payInfo.setOutTradeNo(vo.getOut_trade_no());
        payInfo.setFeeType(vo.getFee_type());
        payInfo.setTotalFee(vo.getTotal_fee());
        payInfo.setSpbillCreateIp(vo.getSpbill_create_ip());
        payInfo.setTimeStart(vo.getTime_start());
        payInfo.setTimeExpire(vo.getTime_expire());
        payInfo.setNotifyUrl(vo.getNotify_url());
        payInfo.setTradeType(vo.getTrade_type());
        payInfo.setProductId(vo.getProduct_id());
        payInfo.setSceneInfo(vo.getScene_info());
        return payInfo;
    }

    private boolean isFail(BaseWxPayResult baseWxPayResult) {
        return !isSuccess(baseWxPayResult);
    }

    private boolean isSuccess(BaseWxPayResult baseWxPayResult) {
        return StringUtils.equals(baseWxPayResult.getReturnCode(), WE_CHAT_RESPONSE_SUCCESS_CODE);
    }

    // ---------------------------------- 小程序订单上传 ----------------------------------

    /**
     * @param orderInfo     订单信息
     * @param merchantOrder 商户订单信息
     */
    @Override
    public void miniAppletOrderExpressUpload(Order orderInfo, MerchantOrder merchantOrder, WechatPayInfo
            wechatPayInfo, String productName) {
        try {
            // 回传成功之后，需要物流信息回填到小程序的发货管理中
            WxMaOrderShippingInfoUploadRequest wxMaOrderShippingInfoUploadRequest = new WxMaOrderShippingInfoUploadRequest();

            final OrderKeyBean orderKeyBean = new OrderKeyBean();
            // 微信支付
            if (Objects.equals(orderInfo.getPayType(), PayConstants.PAY_TYPE_WE_CHAT)) {
                // 订单单号类型，用于确认需要上传详情的订单。枚举值1，使用下单商户号和商户侧单号；枚举值2，使用微信支付单号
                orderKeyBean.setOrderNumberType(2);
                // 原支付交易对应的微信订单号
                orderKeyBean.setTransactionId(orderInfo.getOutTradeNo());
            }

            // 余额支付
            if (Objects.equals(orderInfo.getPayType(), PayConstants.PAY_TYPE_YUE)) {
                orderKeyBean.setOrderNumberType(1);
                // 支付下单商户的商户号，由微信支付生成并下发。
                orderKeyBean.setMchId(wxPayService.getConfig().getMchId());
                // 商户系统内部订单号，只能是数字、大小写字母`_-*`且在同一个商户号下唯一
                orderKeyBean.setOutTradeNo(orderInfo.getPlatOrderNo());
            }

            wxMaOrderShippingInfoUploadRequest.setOrderKey(orderKeyBean);

            // 快递发货
            if (Objects.equals(merchantOrder.getShippingType(), OrderConstants.ORDER_SHIPPING_TYPE_EXPRESS)) {
                // 物流模式 1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品，虚拟商品，例如话费充值，点卡等，无实体配送形式 4、用户自提
                wxMaOrderShippingInfoUploadRequest.setLogisticsType(1);

                OrderType orderType = OrderType.of(orderInfo.getType());
                ExpressOrderType expressOrderType = Objects.equals(OrderType.PURCHASE, orderType) ? ExpressOrderType.PURCHASE : ExpressOrderType.RENT_CREATE;
                OrderInvoiceDTO orderInv = orderInvoiceService.findOrderInvoiceByOrderNoAndInvoiceType(orderInfo.getOrderNo(), expressOrderType);

                ShippingListBean shippingListBean = new ShippingListBean();
                shippingListBean.setTrackingNo(orderInv.getTrackingNumber());
                shippingListBean.setExpressCompany("SF");
                shippingListBean.setItemDesc(productName);

                ContactBean contactBean = new ContactBean();
                contactBean.setReceiverContact(DesensitizedUtil.mobilePhone(merchantOrder.getUserPhone()));
                shippingListBean.setContact(contactBean);

                wxMaOrderShippingInfoUploadRequest.setShippingList(Collections.singletonList(shippingListBean));
            }

            // 自提
            if (Objects.equals(merchantOrder.getShippingType(), OrderConstants.ORDER_SHIPPING_TYPE_PICK_UP)) {
                // 物流模式 1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品，虚拟商品，例如话费充值，点卡等，无实体配送形式 4、用户自提
                wxMaOrderShippingInfoUploadRequest.setLogisticsType(4);

                ShippingListBean shippingListBean = new ShippingListBean();
                shippingListBean.setItemDesc(productName);
                ContactBean contactBean = new ContactBean();
                contactBean.setReceiverContact(DesensitizedUtil.mobilePhone(merchantOrder.getUserPhone()));

                wxMaOrderShippingInfoUploadRequest.setShippingList(Collections.singletonList(shippingListBean));
            }

            // 发货模式枚举值：1、UNIFIED_DELIVERY（统一发货）2、SPLIT_DELIVERY（分拆发货） 示例值: UNIFIED_DELIVERY
            wxMaOrderShippingInfoUploadRequest.setDeliveryMode(1);
            wxMaOrderShippingInfoUploadRequest.setUploadTime(LocalDateTime.now().format(UTC_SIMPLE_MS_PATTERN));

            UserToken userToken = userTokenService.getTokenByUserId(merchantOrder.getUid(), UserConstants.USER_TOKEN_TYPE_ROUTINE);

            PayerBean payerBean = new PayerBean();
            payerBean.setOpenid(userToken.getToken());
            wxMaOrderShippingInfoUploadRequest.setPayer(payerBean);
            log.info("执行上传微信公众号发货信息接口请求: {}", JsonUtil.toJSONString(wxMaOrderShippingInfoUploadRequest));
            WxMaOrderShippingInfoBaseResponse upload = wxMaOrderShippingService.upload(wxMaOrderShippingInfoUploadRequest);
            log.info("执行上传微信公众号发货信息接口响应: {}", JsonUtil.toJSONString(upload));
        } catch (Exception e) {
            log.error("执行上传微信公众号发货信息接口异常，订单号为: {}", orderInfo.getOrderNo(), e);
        }
    }

    private final DateTimeFormatter UTC_SIMPLE_MS_PATTERN = DateTimeFormatter.ofPattern(DatePattern.UTC_MS_PATTERN);
}